Skip to content

feat(hermes-bridge): bridge W2A sensors to Hermes via webhook subscriptions#5

Draft
daibor wants to merge 3 commits intomainfrom
feat/hermes-sensor-bridge
Draft

feat(hermes-bridge): bridge W2A sensors to Hermes via webhook subscriptions#5
daibor wants to merge 3 commits intomainfrom
feat/hermes-sensor-bridge

Conversation

@daibor
Copy link
Copy Markdown
Collaborator

@daibor daibor commented Apr 28, 2026

Summary

New package @world2agent/hermes-sensor-bridge — an independent local supervisor daemon that hosts W2A sensor subprocesses and ships their signals into Hermes Agent via the gateway's native webhook subscriptions. Sibling of claude-code-channel, but Hermes-shaped (out-of-process bridge, fresh AIAgent run per signal, no MCP).

End-to-end exercised against a real hermes gateway run with @world2agent/sensor-hackernews: real HN signals through sensor → supervisor → Hermes → AIAgent — HMAC verified, X-Request-ID dedup verified, real LLM responses generated.

Architecture (high level)

sensor child process              supervisor (parent)              hermes gateway
─────────────────────             ─────────────────────            ──────────────
startSensor + SDK                 read child.stdout line-by-line   /webhooks/<name>
stdoutTransport()       ───→      parse → POST(body, headers)  ──→ HMAC + X-Request-ID
                                  with retry on 5xx/network         dedup, then
                                                                    AIAgent.run_conversation
                                                                    with --skills loaded

Sensor child has zero Hermes knowledge — it's a stock startSensor + SDK stdoutTransport. All Hermes-specific work (HMAC, X-Request-ID, prompt body shape, HTTP retries, route subscription, platform bootstrap) lives in the supervisor. Same runner can be reused by future channels.

What this PR adds

  • src/runner/: bin that runs one sensor and writes signals to stdout, logs to stderr.
  • src/supervisor/: daemon. Spawns/monitors runners, parses stdout signals, POSTs to Hermes with HMAC + X-Request-ID, retries on 5xx/network. Exposes 127.0.0.1 control HTTP for reload/list/health.
  • src/cli/: world2agent-hermes user CLI — start/stop/status/list/add/remove/logs plus hermes-init.
  • skills/world2agent-manage/: agent-facing skill wrapping the CLI for natural-language sensor management.
  • hermes-init (auto-called by add): writes a marker-fenced managed platforms.webhook block to ~/.hermes/config.yaml and WEBHOOK_* to ~/.hermes/.env. Idempotent. Refuses to merge into a hand-managed top-level platforms: block.
  • e2e/: smoke tests for the bootstrap helper (4 states) and delivery worker (HMAC raw hex, X-Request-ID, body shape, 4xx fail-fast, 5xx retry).

Dependencies

  • @world2agent/sdk@0.1.0-alpha.1 (already published). No SDK changes required for this PR — bridge uses existing startSensor, FileSensorStore, stdoutTransport, packageToSkillId.
  • Runtime needs the hermes CLI on PATH for add/remove (which shell out to hermes webhook subscribe / unsubscribe).

Status: WIP

Codex review fed back several issues; most are addressed in this branch:

  • parseSubscribeOutput no longer synthesizes a default subscription name; caller falls back to the name it requested.
  • ✅ Manifest preserves user-provided skill_id (was previously overridden by packageToSkillId(pkg) in normalize/parse).
  • NO_RESTART_EXIT_CODES no longer lists exit 13 (the runner has no startup self-test that produces it; was dead-code).
  • ensureHermesWebhookEnabled heals .env even when config.yaml already says enabled (partial-state recovery).
  • add now passes the bridge HMAC secret through to hermes-init so Hermes's global WEBHOOK_SECRET matches ~/.world2agent/.hmac_secret on first install.

Open items for follow-up:

  • Manifest schema collision if another in-process consumer (e.g. an OpenClaw plugin spike) writes to ~/.world2agent/sensors.json with a different shape (no webhook_url). The bridge currently rejects on parse — needs a per-channel namespace or owner key.
  • Interactive add (Q&A driven by the sensor's SETUP.md) is not implemented; --config-file <path> is required for now. Bridge writes a generic handler skill in this case rather than the sensor-specific one.
  • Add explicit Vitest suite (currently only node e2e/*.mjs smoke tests).

Test plan

  • pnpm run build (TypeScript strict, ESM)
  • node e2e/test-ensure-hermes-webhook.mjs (23/23 PASS — fresh / idempotent / pre-enabled / unmanaged-refuses / partial-heal / nested-agent.platforms-ignored / skill_id round-trip)
  • node e2e/test-delivery.mjs (16/16 PASS — happy path, 4xx fail-fast, 5xx retry exhaust, 5xx-then-200)
  • Real E2E against hermes gateway run with @world2agent/sensor-hackernews: 9 real signals delivered with HTTP 202, X-Request-ID = signal.signal_id, distinct deliveries triggered AIAgent runs producing real LLM responses (kimi-k2.6 + gemini-3-flash-preview)
  • CI wiring (out of scope for this PR)
  • Long-running soak (multi-day) with at least one production sensor

🤖 Generated with Claude Code

daibo@machinepulse.ai and others added 3 commits April 28, 2026 11:57
…ptions

Adds @world2agent/hermes-sensor-bridge: an independent local supervisor
daemon that hosts W2A sensor subprocesses and ships their signals into
Hermes Agent via the gateway's native webhook subscriptions. End-to-end
exercised against a real `hermes gateway run` with @world2agent/sensor-
hackernews — real HN signals delivered, HMAC validated, X-Request-ID
dedup verified, and Hermes ran AIAgent.run_conversation() per signal
with the generated handler skill auto-loaded.

Components:
- runner: thin Node subprocess running a sensor with SDK stdoutTransport.
  Channel-agnostic — ships every signal as a JSON line on stdout, all
  diagnostics on stderr. No Hermes knowledge.
- supervisor daemon: spawns/monitors runners, parses stdout signals,
  POSTs each with X-Webhook-Signature (raw hex HMAC-SHA256) +
  X-Request-ID (= signal.signal_id). Exposes 127.0.0.1 control HTTP for
  reload/list/health. HMAC and webhook URLs stay in supervisor only,
  never leak into child env.
- CLI `world2agent-hermes`: start/stop/status/list/add/remove/logs +
  hermes-init (auto-bootstraps Hermes webhook platform — writes a
  managed `platforms.webhook` block to ~/.hermes/config.yaml and
  WEBHOOK_* to ~/.hermes/.env, marker-fenced and idempotent).
- world2agent-manage skill: agent-facing wrapper around the CLI.

Depends on the public @world2agent/sdk@0.1.0-alpha.1 — uses only
existing exports (startSensor, FileSensorStore, stdoutTransport,
packageToSkillId). No SDK changes required for this PR.

Status: WIP. Smoke tests for the bootstrap helper (4 states) and the
delivery worker (HMAC, X-Request-ID, body shape, 4xx/5xx behaviour)
pass via `node e2e/test-*.mjs` after `pnpm run build`.

Known follow-ups:
- Codex review feedback partially addressed: parseSubscribeOutput no
  longer synthesizes a default name; manifest preserves user-provided
  skill_id; supervisor's NO_RESTART exit-code set is honest; helper
  heals partial-state .env when config.yaml says enabled.
- Manifest write conflict if another in-process consumer also writes
  to ~/.world2agent/sensors.json with a different schema (encountered
  during E2E with a parallel openclaw-plugin spike). Not addressed.
- Interactive setup not implemented; --config-file required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion

The plugin marketplace loads packages from this repo, not from npm, so
shipping dist/ with the source is the established convention. Drops
`dist` from .gitignore and adds the built artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@daibor daibor marked this pull request as draft April 28, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant